﻿//========================================================= 
/**@file FolderLogger.h 
 * @brief 文件夹日志,将文件夹输出到一个固定的目录,同时控制
 * 日志文件总数及每个日志文件的大小,对于超过数据和大小的,删
 * 除最早的一个文件版本 
 * 
 * @date 2014-06-01   18:09:02 
 * @author Zhyioo 
 * @version 1.0
 */ 
//--------------------------------------------------------- 
#ifndef _LIBZHOUYB_FOLDERLOGGER_H_
#define _LIBZHOUYB_FOLDERLOGGER_H_
//--------------------------------------------------------- 
#include "StringLogger.h"
#include "../FolderHelper.h"
//---------------------------------------------------------
/// 默认的日志文件数目
#ifndef LOG_FILE_COUNT
#   define LOG_FILE_COUNT (2)
#endif
/// 默认的日志文件大小
#ifndef LOG_FILE_SIZE
#   define LOG_FILE_SIZE FILE_M(1)
#endif
//--------------------------------------------------------- 
namespace zhou_yb {
namespace extension {
namespace ability {
//--------------------------------------------------------- 
/**
 * @brief 文件夹日志 
 * 
 * 在指定的文件夹下输出日志,日志由N个子文件组成,每个子文件名称按照 
 * "文件夹" + "\子标题" + "时间".log 组成,按照时间顺序进行排序 
 * 每个日志有固定的大小作为限制,超过数目限制时,自动删除时间最早的日志 
 */ 
class FolderLogger : public StringLogger
{
protected:
    /// 最大输出文件数目 
    size_t _logcount;
    /// 当前输出文件大小 
    size_t _logsize;
    /// 最大输出文件大小  
    size_t _maxsize;
    /// 日志标题 
    string _title;
    /// 输出目录路径 
    string _dir;
    /// 当前输出的文件组(按照时间顺序进行排序) 
    list_t<string> _logs;
    
    /// 输出文件流 
    ofstream _fout;

    // 缓冲区事件(计算输出的文件大小) 
    virtual void _buf_after_changed()
    {
        string strVal = StringLogger::String();
        StringLogger::Clear();

        size_t len = strVal.length();
        _logsize += len;

        // 如果超过限制大小重新输出到另外一个文件 
        if(_logsize >= _maxsize)
            _reset_file();

        _fout<<strVal;
    }
    /// 初始化路径 
    void _init_dir(const char* dir)
    {
        _dir = _strput(dir);
        if(_dir.length() < 1)
        {
            _dir = ".";
            _dir += PATH_SEPARATOR;
            _dir += "logs";
        }
        if(_dir[_dir.length() - 1] != PATH_SEPARATOR)
            _dir += PATH_SEPARATOR;
    }
    /// 初始化操作 
    void _init(const char* dir, const char* logTitle, size_t logCount, size_t logMaxSize)
    {
        _os = &_ostr;

        _init_dir(dir);

        _logcount = logCount;
        _title = _strput(logTitle);
        // 默认日志最小为1K
        _maxsize = _max(logMaxSize, FILE_K(1));

        _logsize = 0;
    }
    /// 是否是指定的日志文件名 
    bool _is_log(string& path)
    {
        bool isLog = false;
        if(_title.length() < 1)
        {
            isLog = true;
            // 文件名为:"时间.log";
            for(int i = 0;i < 14; ++i)
            {
                if(_is_digital(path[i]))
                {
                    isLog = false;
                    break;
                }
            }
        }
        else
        {
            // 文件名以Title开头
            ByteArray pathArray(path.c_str(), path.length());
            size_t index = StringConvert::LastIndexOf(pathArray, '_');
            pathArray = pathArray.SubArray(0, index);

            ByteArray titleArray(_title.c_str(), _title.length());
            isLog = StringConvert::Compare(pathArray, titleArray);
        }
        return isLog;
    }
    /// 生成一个新的日志文件名到文件日志组中 
    void _new_log()
    {
        ByteBuilder sys_time(32);
        get_current_systime(sys_time);
        /* "2014-05-26 12:32:15" => "20140526123215" */
        StringConvert::Remove(sys_time, ' ');
        StringConvert::Remove(sys_time, ':');
        StringConvert::Remove(sys_time, '-');

        // 如果在同一秒内产生了多个文件,再次按照序号重新命名 
        string str;
        if(_title.length() > 0)
        {
            str += _title;
            str += "_";
        }
        str += sys_time.GetString();

        if(_logs.size() > 0)
        {
            string& logback = _logs.back();
            // 相同时间内需要产生多个日志,文件名重复,在后续的文件名上加上序号
            if(StringConvert::StartWith(ByteArray(logback.c_str(), logback.length()), ByteArray(str.c_str(), str.length())))
            {
                // 第一次重复
                if(logback.length() == str.length() + 4)
                {
                    str += "_00";
                }
                else
                {
                    char sNum[4] = {0};
                    // title + "_" + 14 + "_00" + ".log"
                    memcpy(sNum, logback.c_str() + _title.length() + 16, 2);
                    int num = atoi(sNum);
                    ++num;
                    sprintf(sNum, "%02d", num);

                    str += "_";
                    str += sNum;
                }
            }
        }

        str += ".log";

        _logs.push_back(str);
    }
    /// 重置日志文件,返回是否重置过 
    bool _reset_file()
    {
        if(_logsize < _maxsize)
            return false;
        // 需要重新输出到另外一个文件中 
        _new_log();
        _fout.flush();
        _fout.close();

        string logpath;
        // 删除旧的日志(只保留链表中最新的一个) 
        while(_logs.size() > _logcount)
        {
            logpath = _dir + _logs.front();
            FolderHelper::EraseFile(logpath.c_str());
            _logs.pop_front();
        }
        _logsize = 0;

        logpath = _dir + _logs.back();
        _fout.open(logpath.c_str(), ios_base::out | ios_base::app);
        // 如果创建文件失败则继续输出到之前的文件 
        if(_fout.fail())
        {
            _logs.pop_back();
            logpath = _dir + _logs.back();
            _fout.open(logpath.c_str(), ios_base::out | ios_base::app);
            return false;
        }
        return true;
    }
    /// 开始记录日志(处理输出的文件名)
    bool _open()
    {
        if(!StringLogger::Open())
            return false;

        list_t<string> logs;
        list_t<size_t> logsize;

        if(!FolderHelper::IsFolderExist(_dir.c_str()))
        {
            if(!FolderHelper::CreateFolder(_dir.c_str()))
            {
                StringLogger::Close();
                return false;
            }
        }
        else
        {
            FolderHelper::EnumFiles(_dir.c_str(), logs, &logsize, ".log");

            // 剔除不是该分组的文件 
            list_t<string>::iterator itr;
            list_t<size_t>::iterator itrSize;

            for(itr = logs.begin(),itrSize = logsize.begin();itr != logs.end();)
            {
                bool isLog = _is_log(*itr);
                if(!isLog)
                {
                    list_t<string>::iterator tmp = itr;
                    list_t<size_t>::iterator tmpSize = itrSize;
                    ++tmp;
                    ++tmpSize;

                    logs.erase(itr);
                    itr = tmp;

                    logsize.erase(itrSize);
                    itrSize = tmpSize;
                }
                else
                {
                    ++itr;
                    ++itrSize;
                }
            }

            if(logs.size() > 0)
            {
                // 按照文件名称时间顺序进行排序(直接插入排序,字符串可以直接排序) 
                list_t<string>::iterator itrInsert;
                for(itr = logs.begin();itr != logs.end(); ++itr)
                {
                    for(itrInsert = _logs.begin();itrInsert != _logs.end(); ++itrInsert)
                    {
                        // 长度小的排在前面 
                        if(itrInsert->length() < itr->length() || (*itrInsert) > (*itr))
                            break;
                    }
                    _logs.insert(itrInsert, (*itr));
                }

                // 设置当前输出的日志文件大小值 
                size_t index = 0;
                for(itr = logs.begin();itr != logs.end(); ++itr)
                {
                    if((*itr) == _logs.back())
                        break;
                    ++index;
                }
                _logsize = *(list_helper<size_t>::index_of(logsize, index));
            }
        }

        // 暂时没有文件或者最后一个日志文件超过设定大小  
        if(_logsize >= _maxsize || logs.size() < 1)
        {
            // 没有文件名称则新建一个日志名称  
            _new_log();
            _logsize = 0;
        }

        string logpath = _dir + _logs.back();
        _fout.open(logpath.c_str(), ios_base::app | ios_base::out);
        if(_fout.fail()) StringLogger::Close();

        return IsOpen();
    }
public:
    /// 默认将日志存放在当前目录logs下,文件名为"时间.log",日志数为5,每个文件大小为256K 
    FolderLogger(bool isOpen = false, const char* dir = "", const char* logTitle = "",
        size_t logCount = 5, size_t logMaxSize = LOG_FILE_SIZE) : StringLogger()
    {
        _init(dir, logTitle, logCount, logMaxSize);
        if(isOpen)
            _open();
    }
    virtual ~FolderLogger() { Close(); }
    //----------------------------------------------------- 
    //@{
    /**@name
     * @brief IBaseDevice成员 
     */
    
    /**
     * @brief 开启日志记录功能
     * @param [in] sArg [default:NULL]
     * - 参数格式:
     *  - NULL 根据上一次配置打开 
     *  - 文件夹路径 
     *  - string FileLogger::FilePathKey 配置项参数 
     * .
     */ 
    virtual bool Open(const char* sArg = NULL)
    {
        if(sArg != NULL)
        {
            ArgParser cfg;
            StringArguments arg;
            cfg.Parse(sArg, arg);
            if(!arg.Contains(FileLogger::FilePathKey))
                _init_dir(sArg);
            else
            {
                string dir = arg[FileLogger::FilePathKey].To<string>();
                if(dir.length() < 1)
                    _init_dir("");
                else
                    _init_dir(dir.c_str());
            }
        }
        Close();

        return _open();
    }
    /// 开启日志功能 
    bool Open(const char* dir, const char* logTitle = "LOG", size_t logCount = 2, size_t logMaxSize = LOG_FILE_SIZE)
    {
        _init(dir, logTitle, logCount, logMaxSize);
        Close();

        return _open();
    }
    /// 是否打开日志 
    virtual bool IsOpen()
    {
        return _fout.is_open() && StringLogger::IsOpen();
    }
    /// 关闭日志记录功能
    virtual void Close()
    {
        if(IsOpen())
        {
            StringLogger::Close();
            _fout.close();
        }
    }
    //@}
    /// 刷新缓冲区 
    virtual void Flush()
    {
        if(IsOpen())
        {
            StringLogger::Flush();
            _buf_after_changed();

            _fout.flush();
        }
    }
    /// 删除整个日志文件夹 
    void Clear()
    {
        StringLogger::Clear();
        if(_dir.length() > 0 && FolderHelper::IsFolderExist(_dir.c_str()))
        {
            FolderHelper::DeleteFolder(_dir.c_str());
        }
    }
    //----------------------------------------------------- 
};
//--------------------------------------------------------- 
} // namespace ability 
} // namespace extension 
} // namespace zhou_yb 
//--------------------------------------------------------- 
#endif // _LIBZHOUYB_FOLDERLOGGER_H_
//========================================================= 
